package ngat.oss;

import java.util.*;
import java.text.*;
import java.io.*;

import jyd.storable.*;
import jyd.collection.*;

import ngat.phase2.*;
import ngat.astrometry.*;
import ngat.util.*;
import ngat.util.logging.*;
import ngat.message.GUI_RCS.*;
import ngat.instrument.*;

import java.rmi.*;

public class TestContention {

    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    public static final double VERY_SMALL = Math.toRadians(0.1);

    public static final long FIXED_GROUP_PRE_START_BUFFER = 5 * 60 * 1000L;
    
    public static final long FIXED_GROUP_POST_START_LAPSE = 10 * 60 * 1000L;

    Root root;
    
    Logger logger;
    
    public double domeLowLimit;
    public double domeHighLimit;
    public double wgtPriority;
    public double wgtLunar;
    public double wgtSeeing;
    public double wgtTrans;
    public double wgtHeight;
    public double wgtMiss;

    public double minimumLunarDistance;    
	
    List candidates; 
    List cache;
    Map history;

    /** The unavailable (RTI) windows - if any.*/
    List windows;

    DefaultMutableTimeProvider timing;
    
    Proposal simProposal;

    long cacheLoadTime = 0L;

    long cacheReloadInterval = 30*60*1000L;

    long windowLoadTime = 0L;

    /** Current weather state.*/
    int weatherState = WeatherMonitoring.WEATHER_UNKNOWN;

    /** How long has the weather been in its current state.*/
    long weatherStable = 0L;

    /** Average length of a good weather period (ms).*/
    double avBadPeriod = 0L;

    /** Average length of a bad weather period (ms).*/
    double avGoodPeriod = 0L;

    /**
     * Buffer time before a fixed-time group is due do we want to exclude other
     * groups from executing in.
     */
    long fixedGroupPreStartBufferTime;
    
    /**
     * How long after a fixed-time group should have started can we still allow
     * it to start.
     */
    long fixedGroupPostStartLapseTime;

    /** Max number of contention samples to calculate .*/
    int npMax;
    
    Site site;

    /** Prime the cache from DB.*/
    public TestContention(boolean simtime) throws Exception {
     
	logger = LogManager.getLogger("SCHEDULE");
	
	cache = new Vector();
	candidates = new Vector();
	history = new HashMap();
	
	timing = new DefaultMutableTimeProvider(0L);
	
	if (simtime)
	    logger.setTimeProvider(timing);
	
    }
    
    public void setRoot(Root root) { this.root = root;}

    public void setWgtHeight(double w)   { this.wgtHeight = w; }
    public void setWgtTrans(double w)    { this.wgtTrans = w; }
    public void setWgtPriority(double w) { this.wgtPriority = w; }
    public void setWgtSeeing(double w)   { this.wgtSeeing = w; }
    public void setWgtLunar(double w)    { this.wgtLunar = w; }
    public void setWgtMiss(double w)     { this.wgtMiss = w; }

    public void setDomeLowLimit(double d) { this.domeLowLimit = d;}

    public void setDomeHighLimit(double d) { this.domeHighLimit = d;}

    public void setMLD(double mld) { this.minimumLunarDistance = mld; }
    
    /** Set the list of unavailable (RT) windows.*/
    public void setUnavailableWindows(List windows) {
	this.windows = windows;
    }

    /** Configure from properties file.*/
    public ConfigurationProperties configure(File file) throws Exception {
	ConfigurationProperties config = new ConfigurationProperties();
	config.load(new FileInputStream(file));
	configure(config);
	return config;
    }

    public void configure(ConfigurationProperties config) throws Exception {

	domeLowLimit   = Math.toRadians(config.getDoubleValue("dome-low-limit", 20.0));
	domeHighLimit   = Math.toRadians(config.getDoubleValue("dome-high-limit", 90.0));

	wgtPriority = config.getDoubleValue("wgt.priority" , 0.4);
	wgtLunar    = config.getDoubleValue("wgt.lunar", 0.25);
	wgtSeeing   = config.getDoubleValue("wgt.seeing", 0.25);
	wgtHeight   = config.getDoubleValue("wgt.height", 0.2);
	wgtTrans    = config.getDoubleValue("wgt.trans", 0.5);
	wgtMiss     = config.getDoubleValue("wgt.miss", 0.4);

	minimumLunarDistance =  Math.toRadians(config.getDoubleValue("mld", 15.0));

	// average length of good/bad periods (hours)
	avBadPeriod  = 3600000.0*config.getDoubleValue("average.bad.period", 6.0);
	avGoodPeriod = 3600000.0*config.getDoubleValue("average.good.period", 6.0);

	// fixed group params
	fixedGroupPreStartBufferTime = config.getLongValue("fixed.group.pre.start.buffer", FIXED_GROUP_PRE_START_BUFFER);
	fixedGroupPostStartLapseTime = config.getLongValue("fixed.group.post.start.lapse", FIXED_GROUP_POST_START_LAPSE);
	
	
	// how many contention calculation samples are allowed.
	npMax = config.getIntValue("max.contention.samples", 25);

    }

    public Group configureGroup(ConfigurationProperties config) throws Exception {
	// extract parameters for group test

	logger.log(1, "Configuring group parameters");

	Group group = null;
	if (config.getProperty("group") != null) {

	    String name = config.getProperty("grp-name", "Test");
	       
	    long gstart = (sdf.parse(config.getProperty("grp-start"))).getTime();
	    long gend   = (sdf.parse(config.getProperty("grp-end"))).getTime();
	     	    
	    if (config.getProperty("monitor") != null) {
		group = new MonitorGroup(name);
		long per = config.getLongValue("period");
		long win = config.getLongValue("window");
		((MonitorGroup)group).setPeriod(per);
		((MonitorGroup)group).setFloatFraction((float)((double)win/(double)per));
		((MonitorGroup)group).setStartDate(gstart);
		((MonitorGroup)group).setEndDate(gend);

	    } else {
		group = new Group(name);
		group.setStartingDate(gstart);
	    }

	    group.setExpiryDate(gend);
	    
	    String lunar = config.getProperty("lunar", "bright");
	    if ("bright".equals(lunar))
		group.setMinimumLunar(Group.BRIGHT);
	    else
		group.setMinimumLunar(Group.DARK);
	    
	    String see = config.getProperty("seeing", "poor");
	    if ("poor".equals(see))
		group.setMinimumSeeing(Group.POOR);
	    else if
		("av".equals(see))
		group.setMinimumSeeing(Group.AVERAGE);
	    else if
		("ex".equals(see))
		group.setMinimumSeeing(Group.EXCELLENT);
	    else
		group.setMinimumSeeing(Group.CRAP);
	    
	    int priority = config.getIntValue("priority", 1);
	    group.setPriority(priority);
	    
	    int nobs = config.getIntValue("grp-nobs", 1);
	    double expose = config.getDoubleValue("expose");
	    int nm = config.getIntValue("mult",1);
	 
	    double ra = Position.parseHMS(config.getProperty("tgt-ra"));
	    double dec = Position.parseDMS(config.getProperty("tgt-dec"));
	    
	    ExtraSolarSource src = new ExtraSolarSource(name+"-tgt");
	    src.setRA(ra);
	    src.setDec(dec);
	    
	    for (int io = 0; io < nobs; io++) {
		Observation obs = new Observation("obs-"+io);
		obs.setExposeTime((float)expose);
		obs.setNumRuns(nm);
		obs.setNumRuns(nm);
		Mosaic mosaic = new Mosaic();
		mosaic.setPattern(Mosaic.SINGLE);
		obs.setMosaic(mosaic);
		obs.setInstrumentConfig(new CCDConfig());
		obs.setSource(src);
		group.addObservation(obs);
	    }
	    
	}
	
	logger.log(1, "Group is: "+group);

	return group;
	
    }
   

    public void loadCache() throws Exception {

	long now = System.currentTimeMillis();

	// Dont bother if we've just loaded the fecker..
	long timeSince = now - cacheLoadTime;
	logger.log(1, "TSCORE::Check for last phase2 cache loading, last loaded "+(timeSince/1000)+"S ago if at all");
	if (timeSince < 10*60*1000L)
	    return;
	
	cache.clear();

	logger.log(2, "TSCORE::Loading phase2 cache...");
	// Prime the cache.

	long st = now;
	int np = 0;
	int nxp = 0;
	int ngp = 0;

	Iterator iTag = root.listAllTags();
	while (iTag.hasNext()) {
	    
	    Tag tag = (Tag)iTag.next();
	    
	    Iterator iUser = tag.listAllUsers();
	    while (iUser.hasNext()) {
		
		User user = (User)iUser.next();
		
		Iterator iProp = user.listAllProposals();
		while (iProp.hasNext()) {
		    
		    KVPair pair = (KVPair)iProp.next();
		    String name = (String)pair.getKey();
		    Path   path = new Path(user.getFullPath(), name);
		    
		    try {
			
			Proposal proposal = OSS_Utilities.fetchProposal(path);
			
			if (proposal == null) continue;
			if (proposal.getActivationDate() > now ||
			    proposal.getExpiryDate() < now) {
			    logger.log(1, "Dumping inactive proposal "+proposal.getName()+
				       "["+sdf.format(new Date(proposal.getActivationDate()))+
				       ", "+sdf.format(new Date(proposal.getExpiryDate())));
			    nxp++;
			    continue;
			}

			// prune out any expired groups - attempt to reduce memory footprint.
			// extra cost during load but may save time during exec.
			Iterator iGroup = proposal.listAllGroups();	
			while (iGroup.hasNext()) {				
			    Group group = (Group)iGroup.next();
			    if (group.getExpiryDate() < now) {
				iGroup.remove();
				ngp++;
			    }
			}
						    
			cache.add(proposal);
			np++;

		    } catch (Exception e) {
			// error loading a proposal....
			e.printStackTrace();
		    }
		}
	    }
	}
	long tt = System.currentTimeMillis() - st;
	logger.log(1, "Loaded "+np+" proposals in "+(tt/1000)+"S, Pruned "+nxp+" inactive proposals and "+ngp+" expired groups in active proposals");
	
	cacheLoadTime = System.currentTimeMillis();

    }

    public void loadUnavailableWindows() {
	// Dont bother if we've just loaded the feckers..
	long timeSince = System.currentTimeMillis() - windowLoadTime;
	logger.log(1, "TSCORE:: Check for last windows loading, last loaded "+(timeSince/1000)+"S ago if at all");
	if (timeSince < 10*60*1000L)
	    return;

	// reload uA windows
	try {
	    AvailabilityModel am = (AvailabilityModel)Naming.lookup("rmi://localhost/AvailabilityModel");
	    
	    List windows = am.getUnavailableTimes(0L,0L);		  
	    setUnavailableWindows(windows);	
	    windowLoadTime = System.currentTimeMillis();
	} catch (Exception e2) {
	    e2.printStackTrace();
	}
	
    }

    public void checkWeather() {
	// Grab the weather info from the RCS/WeatherMonitoring
	weatherState  = WeatherMonitoring.WEATHER_UNKNOWN;
	weatherStable = 0L;
	try {
	    WeatherMonitoring wm = (WeatherMonitoring)Naming.lookup("rmi://localhost/WeatherMonitoring");

	    weatherState  = wm.getWeatherState();
	    weatherStable = wm.getWeatherStableTime();

	} catch (Exception e2) {
	    e2.printStackTrace();
	}

    }

    /** Add the simulation proposal to the cache.*/
    public void addProposalToCache(Proposal proposal) {
	simProposal = proposal;
	cache.add(simProposal);
    }

    /** Add the supplied test group into the simulation test proposal and cache.*/
    public void addTestGroup(Group group) {
	simProposal.addGroup(group);
	logger.log(2, "Adding test group "+group.getName()+" to simulation proposal");
    }
    
    public void loadHistory() {

	history.clear();

	logger.log(2, "Loading execution history cache...");

	long st = System.currentTimeMillis();
        int nh = 0;
	
	Iterator ip = cache.iterator();
        
	while (ip.hasNext()) {
	    
            try {
		
                Proposal proposal = (Proposal)ip.next();
		
                if (proposal == null) continue;
		
		Iterator iGroup = proposal.listAllGroups();
                logger.log(3, "Scheduler", "Checking groups in proposal "+proposal.getFullPath()+"...");
		
                while (iGroup.hasNext()) {
		    
                    Group group = (Group)iGroup.next();
		    
		    // only mgs need multiple done dates...		    
		    if (group instanceof MonitorGroup) {
			MonitorGroup mg = (MonitorGroup)group;
			long when = mg.getLastCompleted();
			history.put(group.getFullPath(), new Long(when));
			nh++;
		    } else {
			
			if (group.isDone()) {
			    history.put(group.getFullPath(), new Long(group.getDoneDate()));
			    nh++;
			}
		    }
		    
		} // next group
		
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	    
	} // next prop
	
	long tt = System.currentTimeMillis() - st;
        logger.log(2, "Loaded "+nh+" exec histories in "+(tt/1000)+"S");

    }

    /** Calculate the contention stats for time with specified seeing.*/
    public double getContention(long time, int seeing) {
	
	candidates.clear();


	Position sun = Astrometry.getSolarPosition(time);
	if (sun.getAltitude(time, site) > Math.toRadians(-5.0)) {
	    logger.log(3, "Scheduler", sdf.format(new Date(time))+"** DAYTIME");
	    return -1.0;	    
	}

	int gc = 0;
	
	// Sift thro all tag/user/props.
	
	
	Iterator ip = cache.iterator();
	while (ip.hasNext()) {
	    
	    try {
		
		Proposal proposal = (Proposal)ip.next();
		
		if (proposal == null) continue;
		
		logger.log(3, "Scheduler", "Checking Proposal: "+proposal.getFullPath());
		
		// Outside activation period.
		logger.log(3, "Scheduler", "Checking activation period...");
		if (proposal.getActivationDate() > time ||
		    proposal.getExpiryDate() < time) {
		    logger.log(3, "Scheduler", "Proposal outside activation period: "+
			       sdf.format(new Date(proposal.getActivationDate()))+" - "+
			       sdf.format(new Date(proposal.getExpiryDate())));			
		    continue;
		}		    
		
		// No time left.
		logger.log(3, "Scheduler", "Checking for time left...");
		if (proposal.getUsedTime() >= proposal.getAllocatedTime()) {
		    logger.log(3, "Scheduler", "Proposal out of time: Used: "+proposal.getUsedTime()+
			       " of "+proposal.getAllocatedTime());
		    continue;
		}
		
		Iterator iGroup = proposal.listAllGroups();
		logger.log(3, "Scheduler", "Checking groups...");
		
		while (iGroup.hasNext()) {
		    
		    Group group = (Group)iGroup.next();
		    
		    logger.log(3, "Scheduler", "Checking group "+group.getName());
		    
		    long exec = getExecTime(group);

		    // Check timing.
		    if (canDo(group, time, exec, seeing, false).canDo) {
			
			Score metric = getScore(group, time, seeing);
			double score = metric.score;	
			//long ttx = getTtx(group, time, exec);
			candidates.add(metric);
			gc++;
			logger.log(3, "Scheduler", "Group is feasible, with score "+score);
			
		    }
		    
		    
		} // next (group)
		
	    } catch (Exception e) {
		// error processing a proposal....
		e.printStackTrace();
	    }
	    
	} // next (proposal)
	
	return (double)gc;
	
    }
    
    /** Returns tue if group can be executed at time.
     * @param group The group to test.
     * @param time When we want to test.
     * @param exec How long it takes to run.
     * @param seeing What is the seeing.
     * @param log Do we want to log the details.
     */
    public CanDo canDo(Group group, long time, long exec, int seeing, boolean log) {

	if (group.getExpiryDate() < time) {
	    if (log)
		logger.log(1, "Group "+group.getName()+" failed: EXPIRED");	
	    return new CanDo(false, "EXPIRED");
	}

	Position sun = Astrometry.getSolarPosition(time);
	if (sun.getAltitude(time, site) > Math.toRadians(-5.0)) {
	    logger.log(1, "Group "+group.getName()+" failed: SUN_IS-UP");
	    return new CanDo(false,"SUN_IS-UP");	    
	}

	// check if the group can complete before sunrise..
	Position sunLater = Astrometry.getSolarPosition(time+exec);

	if (sunLater.getAltitude(time+exec, site) > Math.toRadians(-12.0)) {
	    if (log)
		logger.log(1, "Group "+group.getName()+" failed: SUN_WILL_RISE");
            return new CanDo(false,"SUN_WILL_RISE");
        }

	// check against the various UA windows.

	if (overlapsWindow(time, time+exec)) {
	    if (log)
		logger.log(1, "Group "+group.getName()+" failed: UA_WINDOW_OVERLAP");
	    return new CanDo(false, "UA_WINDOW_OVERLAP");
	}

	// check if there are any Fixed groups which overlap

	if (overlapsFixedGroup(time, time+exec)) {
	    if (log)
		logger.log(1, "Group "+group.getName()+" failed: UA_WINDOW_OVERLAP");
	    return new CanDo(false, "FIXED_GRP_OVERLAP");

	}
	
	if (group instanceof MonitorGroup) {
	    MonitorGroup mg = (MonitorGroup)group;
	    long   startDate     = mg.getStartDate();
	    long   period        = mg.getPeriod();
	    float  floatFraction = mg.getFloatFraction();
	    
	    double fPeriod = (double)( time - startDate ) / (double)period;
	    double iPeriod = Math.rint(fPeriod);
	    
	    if (startDate > time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: MG_PRE_START");
		return new CanDo(false,"MG_PRE_START");
	    }

	    long endDate = mg.getEndDate();
	    if (endDate <= time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: MG_POST_END");
		return new CanDo(false,"MG_POST_END");
	    }

	    long startFloat = startDate + 
		(long)((iPeriod - (double)floatFraction / 2.0)* (double)period);
	    long endFloat   = startDate + 
		(long)((iPeriod + (double)floatFraction / 2.0)* (double)period);
	    
	    if ( ! ((startFloat <= time) && (endFloat >= time)) ) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: MG_OUT_WINDOW");
		return new CanDo(false,"MG_OUT_WINDOW");
	    }

	    // check wasnt done this window
	    long lastDone = getLastExecution(group);
	    if (lastDone > startFloat && lastDone < endFloat) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: MG_EXEC_IN_WINDOW");
		return new CanDo(false,"MG_EXEC_IN_WINDOW");
	    }

	} else if
	    (group instanceof EphemerisGroup) {
	    
	    EphemerisGroup eg = (EphemerisGroup)group;
	    
	    long   startDate     = eg.getStart();
	    long   endDate       = eg.getEnd();
	    long   period        = eg.getPeriod();
	    double phase         = (double)eg.getPhase();
	    double slopFraction  = (double)eg.getSlopPhase();
	    
	    double fperiod = Math.floor((time - startDate)/period);
	    int iperiod = (int)fperiod;
	    
	    if (startDate > time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: EG_PRE_START");
		return new CanDo(false,"EG_PRE_START");
	    }

	    if (endDate <= time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: EG_POST_END");
		return new CanDo(false,"EG_POST_END");
	    }

	    long startWindow = startDate + (long)((fperiod + phase - slopFraction/2.0) * period);
	    long endWindow   = startDate + (long)((fperiod + phase + slopFraction/2.0) * period);
	    
	    if ( ! ((startWindow <= time) && (endWindow >= time))) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: EG_OUTSIDE_SLOP");
		return new CanDo(false,"EG_OUTSIDE_SLOP");
	    }

	    // fail if it has been done by time.
            long lastDone = getLastExecution(group);
            if (lastDone < time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: EG_DONE");
                return new CanDo(false,"EG_DONE");
	    }

	} else if
	    (group instanceof FixedGroup) {

	    // fail if it has been done by time.
	    long lastDone = getLastExecution(group);
	    if (lastDone < time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: FIXG_DONE");
		return new CanDo(false,"FIXG_DONE");
	    }

	    FixedGroup fg = (FixedGroup)group;
	    
	    // FG can be done within 5 minutes of the fixed time.
	    long fixed = fg.getFixedTime();
	    if ((fixed < (time - 300000L)) ||
		(fixed > (time + 300000L))) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: FG_OUT_SLOP");
		return new CanDo(false,"FG_OUT_SLOP");
	    }

	} else {

	    if (group.getStartingDate() > time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: FLEX_PRE_START");
		return new CanDo(false,"FLEX_PRE_START");
	    }

	    if (group.getExpiryDate() < time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: FLEX_POST_END");
		return new CanDo(false,"FLEX_POST_END");
	    }

	    // we can always do a flexible group - thats why they are called flexible !
	    // unless it was already done by time.
	    long lastDone = getLastExecution(group);
	    if (lastDone > 0 && lastDone <= time) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: FLEX_DONE");
		return new CanDo(false,"FLEX_DONE");
	    }
	}
	
	// check the other constraints.
	
	// is seeing OK ?
	if (seeing < group.getMinimumSeeing()) {
	    if (log)
                logger.log(1, "Group "+group.getName()+" failed: SEEING_BAD");
	    return new CanDo(false, "SEEING_BAD");
	}

	// is moon up/down/close
	Position moon = Astrometry.getLunarPosition(time);
	boolean moonup = moon.getAltitude(time, site) > Math.toRadians(-2.0);
	if (group.getMinimumLunar() == Group.DARK && moonup) {
	    //System.err.println("Moon up fail: "+group.getFullPath());
	    if (log)
                logger.log(1, "Group "+group.getName()+" failed: DARK_MOON_UP");
	    return new CanDo(false,"DARK_MOON_UP");
	} 
	
	// are all targets visible.
	int nobs = 0;
	Iterator iobs = group.listAllObservations();
	while (iobs.hasNext()) {
	    Observation obs = (Observation)iobs.next();
	   
	    Source src = obs.getSource();
	    if (src == null) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: NO_TARGET");
		return new CanDo(false,"NO_TARGET");
	    }
	    Position target = src.getPosition();
	    
	    double elev = target.getAltitude(time, site);
	    
	    if (elev < domeLowLimit) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: TARGET_TOO_LOW");
		return new CanDo(false,"TARGET_TOO_LOW");
	    }
	    
	    if (elev > domeHighLimit) {
		if (log)
		    logger.log(1, "Group "+group.getName()+" failed: TARGET_TOO_HIGH");
		return new CanDo(false,"TARGET_TOO_HIGH");
	    }


	    // check all targets are far enough from moon
	    double lunarDistance = target.getAngularDistance(moon);

	    if (group.getMinimumLunar() == Group.BRIGHT && moonup) {
                //System.err.println("Moon up fail: "+group.getFullPath());

		double mld = group.getMinimumLunarDistance();
		if (mld <= 0.0 ) {
		    // None specified - use default
		    if (lunarDistance < minimumLunarDistance) {
			if (log)
			    logger.log(1, "Group "+group.getName()+" failed: BRIGHT_MOON_DEF_CLOSE mld= "+minimumLunarDistance+" ld= "+lunarDistance);
			return new CanDo(false, "BRIGHT_MOON_DEF_CLOSE");
		    }
		} else {
		    // Group has specific MLD.
		    if (lunarDistance < mld) {
			if (log)
			    logger.log(1, "Group "+group.getName()+" failed: BRIGHT_MOON_SPEC_CLOSE mld= "+mld+" ld= "+lunarDistance);
			return new CanDo(false,"BRIGHT_MOON_SPEC_CLOSE");
		    }
		}
		
	    }

	    InstrumentConfig cfg = obs.getInstrumentConfig();
	    // TODO Check the config is valid against known instruments' valid configs

	    if (cfg == null) {
		if (log)
		    logger.log(1, "Group "+group.getName()+
			       " failed: NO_INST_CONFIG");
		return new CanDo(false, "NO_INST_CONFIG");
	    }

	    // START New Instrument checking
	    // only do full check if needed...	    
	    if (System.getProperty("full.inst.check") != null) {
	    // More clever inst/config testing
	    // See if we recognise the instrument.
	    Instrument inst = TelescopeStatus.getInstrumentFor(cfg);	
	    
	    if (inst == null) {		
		logger.log(1, "Group "+group.getName()+
			   " failed: UNKNOWN_INST_CLASS: "+cfg.getClass().getName());		   
		return new CanDo(false,"UNKNOWN_INST_CLASS");    
	    }
	    
	    // Check if currently available... should we be doing this under scoring ?
	    // after all if the RCS is not running we dont yet have this info ...

	    // TODO all the more reason to get ICM up and running full-time

// 	    if (inst.getStatus() == Instrument.OFFLINE) {
// 		logger.log(1, "Group "+group.getName()+
// 			   " failed:  INST_OFFLINE inst= "+inst.getName());
// 		return new CanDo(false,"INST_OFFLINE");
// 	    }
		
	    // Check the Instrument config is actually valid.
	    if (! (inst.canBeConfigured(cfg))) {
		logger.log(1, "Group "+group.getName()+
			   " failed: INVALID_INST_CONFIG: "+cfg);
		return new CanDo(false,"INVALID_INST_CONFIG");
	    }
	    }
	    // END New Instrument checking

	    nobs++;
	}
	if (nobs == 0) {	    
	    if (log)
                logger.log(1, "Group "+group.getName()+" failed: NO_OBS_IN_GROUP");
	    return new CanDo(false,"NO_OBS_IN_GROUP");
	}

	// can we do before sunrise-delta
	
	return new CanDo(true);
	
    }

    /** Calculate Time left to execute (ttx) for a group at a specified time.*/
    private long getTtx(Group group, long time, long exec) {

	// calculate the current window at t.

	long endWindow = time;

	if (group instanceof MonitorGroup) {
	    
	    MonitorGroup mg = (MonitorGroup)group;
	    
	    long   startDate     = mg.getStartDate();
	    long   period        = mg.getPeriod();
	    float  floatFraction = mg.getFloatFraction();
	    
	    double fPeriod = (double)( time - startDate ) / (double)period;
	    double iPeriod = Math.rint(fPeriod);
	    
	    long endDate = mg.getEndDate();
	    
	    long startFloat = startDate + 
		(long)((iPeriod - (double)floatFraction / 2.0)* (double)period);
	    long endFloat   = startDate + 
		(long)((iPeriod + (double)floatFraction / 2.0)* (double)period);
	    
	    if ((startFloat <= time) && (endFloat >= time)) {
		endWindow = endFloat;
	    } else 
		return 0L;
	    
	} else if 
	    (group instanceof EphemerisGroup) {
	    
	    EphemerisGroup eg = (EphemerisGroup)group;

	    // TODO - need to chekc each of the available ephem windows for visibility etc.
	    // i.e. current window is really ALL possible windows ORed.
	    long   startDate     = eg.getStart();
	    long   endDate       = eg.getEnd();

	    if (startDate > time || endDate < time)
		return 0L;

	    // this is just a fudge for now to allow the same code to process all groups
	    endWindow = endDate;

	} else if
	    (group instanceof FixedGroup) {

	    // TODO - just need the slop amount - is time in that slop?
	    long buffer = 5*60*1000L; // hard coded 5 MIN buffer.
	    
	    long fixedTime = ((FixedGroup)group).getFixedTime();

	    if (((fixedTime - buffer) > time) || ((fixedTime + buffer) < time))
		return 0L;
		
	    endWindow = fixedTime + buffer;

	} else {

	    // TODO - use starting date upto expiry date.

	    long startDate = group.getStartingDate();
	    long expiryDate = group.getExpiryDate();

	    if ((startDate > time) || (expiryDate < time))
		return 0L;

	    endWindow = expiryDate;

	}
	 
	// now loop from now to endWindow with a gap determined by the length of the window but
	// no more than 1000 slots allowed and no less than 60 sec.

	long dt = (endWindow - time)/50;
	if (dt < 60*1000L)
	    dt = 60*1000L;

	long ttx = 0L;
	long ts = time;
	while (ts < endWindow) {
	    if (canDo(group, ts, exec, Group.EXCELLENT, false).canDo)
		ttx += dt;
	    ts += dt;
	}

	return ttx;

    }

    /** Update the history for a group.*/
    private void updateHistory(Group group, long when) {
	history.put(group.getFullPath(), new Long(when));
    }
    
    /** When did this group get done last..*/
    private long getLastExecution(Group group) {    
	Long when = (Long)history.get(group.getFullPath());
	if (when != null)
	    return when.longValue();
	
	return -1L;
	
    }
    
    /** Return the score for group at time.*/
    public Score getScore(Group group, long time, int seeing) {
	
	double score = 0.0;
	
	int nobs = 0;
	Iterator io = group.listAllObservations();
	while (io.hasNext()) {
	    
	    Observation obs = (Observation)io.next();
	    
	    Source src = obs.getSource();
	    Position target = src.getPosition();
	    
	    double elev = target.getAltitude(time, site);	
	    double tth = target.getTransitHeight();
	    if (Math.abs(tth) < VERY_SMALL)
		tth = VERY_SMALL;
	    
	    score += scoreHeight(elev);
	    score += scoreTransit(elev/tth);
	    
	    nobs++;
	    
	}

	score /= nobs;
	if (Double.isInfinite(score))
	    logger.log(1, "Inf value for score obs: "+group.getFullPath()+" Num.obs = "+nobs);
		       

	// Using assigned group priorities.
	double score1 = 0.0;
	switch (group.getPriority()) {
	case -2:
	    // Make it very low so a normal never looses out.	    
	    score1 = -10;
	    break;
	case -1:
	    // For now we use p=3 which is High.
	    score1 = wgtPriority*3.0;	   
	    break;
	default:
	    score1 = wgtPriority*(double)group.getPriority();
	    break;
	}
	score += score1;
	
	// moon dark/bright
	int requestedLunar = group.getMinimumLunar();
	int actualLunar = Group.BRIGHT;
	Position moon = Astrometry.getLunarPosition(time);
	boolean moonup = moon.getAltitude(time, site) > Math.toRadians(-2.0);
	if (moonup) 
	    actualLunar = Group.BRIGHT;
	else	     
	    actualLunar = Group.DARK;

	double score_lun = wgtLunar/(1.0 + (actualLunar- requestedLunar));
	if (Double.isInfinite(score_lun))
            logger.log(1, "Inf value for score lun: "+group.getFullPath()+
		       " rl="+Group.toLunarString(requestedLunar)+
		       " l="+Group.toLunarString(actualLunar));
	
	score += score_lun;
	
	// seeing estimate.
	int requestedSeeing = group.getMinimumSeeing();
	double score_see = wgtSeeing/(1.0 + (seeing - requestedSeeing));
	if (Double.isInfinite(score_see)) 
	    logger.log(1, "Inf value for score see: "+group.getFullPath()+
		       " rs="+Group.toSeeingString(requestedSeeing)+
		       " s="+Group.toSeeingString(seeing));
	score += score_see;
	
	// missed window effect.

	if (group instanceof MonitorGroup) {
	    MonitorGroup mg = (MonitorGroup)group;
	    double mw = Math.max( (double)((time - getLastExecution(mg))/mg.getPeriod())-1.0, 0.0);  ;
	    score += (1.0-Math.exp(-mw/2.0))*wgtMiss;
	}
	
	return new Score(group, score, 0L);
	
    }
    
    private double scoreHeight(double h) {
	double hh = Math.toDegrees(h);
	if (hh < 20.0)
	    return wgtHeight*hh/90.0;
	else
	    return wgtHeight*(1.0+(hh-20.0)/70.0);
    }
    
    private double scoreTransit(double t) {
	if (t < 0.3) 
	    return wgtTrans*t;
	else
	    return wgtTrans*(0.4*(t-0.3)/0.7+0.6);
    }
    
    private long getExecTime(Group group) {
	
	int mosaic     = 0; 
	int multruns   = 1;
	int countObs   = 0; // count obs.
	int countMoves = 0; // counts distinct source moves.
	
	Observation observation = null;	   
	Source      source      = null;	
	Source  prevSource      = null; 	
	Position    target      = null;
	Position    prevTarget  = null;

	//Instrument inst = null;
	//InstrumentConfig instConfig = null;

	double maximalObserveTime = 0.0;
	double nominalObserveTime = 0.0;

	double slewTime = 0.0;
	double maximalExecutionTime = 0.0;
	double nominalExecutionTime = 0.0;


	// check for sequencing here and create it if it doesnt exist...
	Vector v = null;
	v = group.getSequence();
	if (v == null || v.size() == 0) {
	    v = new Vector();
	    Iterator it = group.listAllObservations();	
	    while (it.hasNext()) {
		observation = (Observation)it.next();
		v.add(observation.getName());
	    }
	}
	
	int nseq = v.size();

	Iterator is = v.iterator();
	while (is.hasNext()) {
	    String obsName = (String)is.next();
	    //observation = (Observation)it.next();
	    observation = group.findObservation(obsName);
	    if (observation == null)
		continue;
	    countObs++;
	    //observation = (Observation)it.next();	   
	    
	    //  ## we dont have access to this OSS stuff - but RCS does have this in InstRegy
	    //instConfig = observation.getInstrumentConfig();
	    //inst       = TelescopeStatus.getInstrumentFor(instConfig);	

	    source      = observation.getSource();
	    if (source == null)
		continue;
	    target      = source.getPosition();
	  
	    // If this is NOT first obs. There IS a previous source.
	    if (countObs != 1) {
		if (source != prevSource) {
		    // New source. Add Slew time. (this is for nominal and maximal.
		    slewTime += target.getSlewTime(prevTarget, Math.toRadians(2.0), Math.toRadians(2.0));
		    countMoves++;
		}		
	    }
	    prevSource = source;
	    prevTarget = target;

	    // Add up all the contributions to this Obo's time.
	    
	    // Mosaics.
	    mosaic = 1;
	    switch (observation.getMosaic().getPattern()) {	   
	    case Mosaic.ARRAY:
		mosaic = observation.getMosaic().getCellsRA()*observation.getMosaic().getCellsDec();
		break;	 
	    case Mosaic.SINGLE:
		mosaic = 1;
		break;
	    case Mosaic.VERTICAL:
	    case Mosaic.HORIZONTAL:
	    case Mosaic.SLOPE_UP:
	    case Mosaic.SLOPE_DOWN:
		mosaic = 3;
		break;
	    case Mosaic.CROSS:	   
	    case Mosaic.X:
		mosaic = 5;	  
		break;
	    case Mosaic.HOLE:
		mosaic = 8;  
		break;
	    case Mosaic.GRID:
		mosaic = 9;  
		break;
	    default:
		mosaic = 1;
	    }
	
	    // Multruns.
	    multruns = observation.getNumRuns();
	   
	    // Maximal time for an observation.
	    maximalObserveTime = mosaic * 
		(Group.MAXIMAL_OFFSET_TIME + multruns * 
		 (observation.getExposeTime() + Group.MAXIMAL_READOUT_TIME + Group.MAXIMAL_DPRT_TIME));
	    
	    maximalExecutionTime += maximalObserveTime;
	    
	    // Nominal time for an observation.
	    nominalObserveTime = mosaic * 
		(Group.NOMINAL_OFFSET_TIME + multruns *
		 (observation.getExposeTime() + Group.NOMINAL_READOUT_TIME));
	    
	    nominalExecutionTime += nominalObserveTime;
	    
	} // per observation.
	
	// Total slew + moves * acquire + obs * config. + 30S for init slew+settling
	nominalExecutionTime += slewTime + Group.NOMINAL_ACQUIRE_TIME + 30000L+
	    countMoves * Group.NOMINAL_ACQUIRE_TIME + 
	    countObs * Group.NOMINAL_CONFIGURATION_TIME;
	
	// total_slew + moves * acquire + obs * config + possible_unwrap + max_slew_to_next_source
	maximalExecutionTime += slewTime + 
	    countMoves * Group.MAXIMAL_ACQUIRE_TIME +
	    countObs * Group.MAXIMAL_CONFIGURATION_TIME +
	    Group.WRAP_TIME + Group.MAXIMAL_SLEW_TIME;
	
	//	System.err.println("Group: "+group.getFullPath()+
	//	   "\n\tMaximal exec time: "+(maximalExecutionTime/1000.0)+"S"+
	//	   "\n\tNominal exec time: "+(nominalExecutionTime/1000.0)+"S"+
	//	   "\n\tIntern slew time:  "+(slewTime/1000.0)+"S"+
	//	   "\n\tObs/seq:"+countObs+
	//	   "\n\tMoves:  "+countMoves);
	
	return (long)nominalExecutionTime;
    }

    /** True if the specified time overlaps any UA window.*/
    public boolean overlapsWindow(long start, long end) {

	// No windows so cant overlap
	if (windows == null)
	    return false;

	Iterator it = windows.iterator();
	while (it.hasNext()) {
	    
	    TimeWindow w = (TimeWindow)it.next();
	    
	    // work out the overlap if any..
	    
	    //System.err.println("working out overlap if any");
	    //System.err.println("g.start = "+sdf.format(new Date(start)));
	    //System.err.println("g.end   = "+sdf.format(new Date(end)));
	    //System.err.println("w.start = "+sdf.format(new Date(w.start)));
	    //System.err.println("w.end   = "+sdf.format(new Date(w.end)));
	    
	    long tos = Math.max(start, w.start);
	    long toe = Math.min(end, w.end);
	    
	    
	    // if we found any overlap, then were scuppered
	    if (tos < toe) {
		//System.err.println("Group exec overlaps a window..["+
		//	   sdf.format(new Date(tos))+", "+sdf.format(new Date(toe))+"]");		
		return true;
	    }
	}

	// no overlaps - okay
	return false;
    }

    /** True if the specified time overlaps any FixedGroup.
     * @param start Time the test window starts.
     * @param end   Time the test window ends.
     */
    public boolean overlapsFixedGroup(long start, long end) {

	Iterator it = Scheduling.listFixedGroups();
	while (it.hasNext()) {
	    
	    FixedGroup fixed = (FixedGroup)it.next();
	    
	    // work out the overlap if any..
	    long fgtime = fixed.getFixedTime();
	    long fgexec = getExecTime(fixed);
		    
	    long tos = Math.max(start, fgtime - fixedGroupPreStartBufferTime );
	    long toe = Math.min(end, fgtime + fixedGroupPostStartLapseTime+ fgexec);
	    
	    
	    // if we found any overlap, then were scuppered
	    if (tos < toe) {
		return true;
	    }
	}
	
	// no overlaps - okay
	return false;
    }



    public List getCandidates() { 
	return candidates;
    }


    public static void main(String args[]) {
	
	Group.NOMINAL_OFFSET_TIME        = 10000L;
	Group.NOMINAL_ACQUIRE_TIME       = 60000L;
	Group.NOMINAL_READOUT_TIME       = 20000L;
	Group.NOMINAL_CONFIGURATION_TIME = 10000L;

	// decide what we want to do now...


	try {
	    
	    Logger logger = LogManager.getLogger("SCHEDULE");
	    logger.setLogLevel(2);
	    LogHandler console = new ConsoleLogHandler(new BasicLogFormatter(150));
	    console.setLogLevel(3);
	    logger.addHandler(console);
	    
	    CommandTokenizer parser = new CommandTokenizer("--");
	    parser.parse(args);
	    ConfigurationProperties cmdargs = parser.getMap();

	    int ll = cmdargs.getIntValue("log-level", 3);
	    logger.setLogLevel(ll);

	    double lat = Math.toRadians(cmdargs.getDoubleValue("lat"));
	    double lon = Math.toRadians(cmdargs.getDoubleValue("long"));
	
	    Position.setViewpoint(lat,lon);

	    System.setProperty("astrometry.impl", "ngat.astrometry.TestCalculator");
	    
	    File   rootDir  = new File(cmdargs.getProperty("jydodb"));
	    String rootName = cmdargs.getProperty("root");
	    
	    System.setProperty("jydodb", rootDir.getPath());
	    
	    System.err.println("Trying to locate root: "+rootName+" at "+rootDir.getPath());
	    
	    OSS_Utilities.setDatabaseDir(rootDir);
	    OSS_Utilities.setRootName(rootName);
	    
	    Root root = OSS_Utilities.fetchRoot();

	    File file = new File(cmdargs.getProperty("config"));

	    boolean sim = (cmdargs.getProperty("simulate") != null);

	    TestContention test = new TestContention(sim);
	    test.setRoot(root);

	    ConfigurationProperties config = test.configure(file);
	     
	    test.loadCache();

	    String option = config.getProperty("opt");
	    
	    Group group = test.configureGroup(config);
	    
	    if (option.equals("test")) {
		test.runTest(config, group);
	    } else if
		(option.equals("sim")) {
		test.runSim(config, group);
	    } else if
		(option.equals("score")) {
		test.runScore(config, group);
	    } else if
		(option.equals("simbin")) {
		test.runSimBin(config, group);
	    } else if
		(option.equals("expect")) {
		test.runExpectBin(config, group);
	    }


	} catch (Exception e) {
	    e.printStackTrace();
	    return;
	}
    }

    public void runTest(ConfigurationProperties config, Group testGroup) throws Exception {
	    
	long time = sdf.parse(config.getProperty("at")).getTime();
	runTestActual(testGroup, time);
	
    }

    public ScoreInfo runTestActual(Group testGroup, long time) {

	logger.log(1, "Start simulation [CONTENTION]");

	// Record why we couldnt schedule the group at time.
	//failureReasons.clear();

	long now = System.currentTimeMillis();

	ScoreInfo info = new ScoreInfo();
	
	double resolution = 0.0; // Anything...
	int ns = 1; // Number of in-window samples - this is same as nt for flex but should be much less for monitors.
	int nn = 1; // Total samples i.e. whole interval split by resolution.
	int nw = 1; // Number of windows. For flex = 1, for mon = (end - start)/period.

	logger.log(1, "Starting contention calculations using maximum sample count: "+npMax);
	// START Redefine sampling
	if (testGroup instanceof MonitorGroup) {
	    // Monitor: Try window/5 then scale down until less than NPMax samples
	    long s1 = Math.max(((MonitorGroup)testGroup).getStartDate(), now);
            long s2 = ((MonitorGroup)testGroup).getEndDate();
            double period = (double)((MonitorGroup)testGroup).getPeriod();
            double window = (double)period*(double)((MonitorGroup)testGroup).getFloatFraction();
	    
	    nw = 1 + (int)((s2 - s1)/period); // Number of windows.
	
	    resolution = window/5.0;	
	    ns = (int)((double)nw*(window/resolution)); // = 5* nw
	    nn = (int)((double)(s2 - s1)/resolution);
	    logger.log(1, "Setting resolution for monitor to "+(resolution/1000)+
		       " S with "+ns+" in-window samples out of "+nn+" total");

	    // now re-adjust back to a sensible number to avoid overloading the processor.
	    while (ns > npMax) {
		resolution *= 2.0;
		ns = (int)((double)nw*(window/resolution)); 
		nn = (int)((double)(s2 - s1)/resolution);
		logger.log(1, "Adjusting resolution for monitor to "+(resolution/1000)+
			   " S with "+ns+" in-window samples out of "+nn+" total");
	    }
	    

	} else {

	    // Flex: Just use NPmax
	    long s1 = Math.max(testGroup.getStartingDate(), now);
            long s2 = testGroup.getExpiryDate();
	    resolution = (s2 - s1)/npMax;
            ns = (int)((double)(s2 - s1)/resolution);  
	    nn = ns;
	    logger.log(1, "Setting resolution for flexible to "+(resolution/1000)+
		       " S with "+ns+" in-window samples out of "+nn+" total");
	}
	// END

	// record ranks
	int[] rp = new int[nn];
	int[] ra = new int[nn];
	int[] rx = new int[nn];

	// record scoring value
	double[] sp   = new double[nn];
	double[] sa   = new double[nn];
	double[] sx   = new double[nn];
	double[] sall = new double[nn];
	
	// record contention 
	int[] cp = new int[nn];
	int[] ca = new int[nn];
	int[] cx = new int[nn];

	for (int i = 0; i < nn; i++) {
	    rp[i] = 0;
	    ra[i] = 0;
	    rx[i] = 0;
	    sp[i] = 0.0;
	    sa[i] = 0.0;
	    sx[i] = 0.0;
	    sall[i] = 0.0;
	    cp[i] = 0;
	    ca[i] = 0;
	    cx[i] = 0;
	}

	// count up valid windows for this group.
	// TODO need to work out how many windows are available,
	// from start to s+nn,
	// so can decide at any time which windo in then decide if
	// the group will be scheduled that window


 	int nwin = 1;
	if (testGroup instanceof MonitorGroup) {
 	    nwin = (int)((((MonitorGroup)testGroup).getEndDate() - time)/((MonitorGroup)testGroup).getPeriod()) + 1;
	    logger.log(1, "Test group is monitor: Period = "+(((MonitorGroup)testGroup).getPeriod()/1000)+" S"+
		       " Number exec windows = "+nwin+
		       " Number time samples = "+nn+
		       " at "+(resolution/1000)+" S");
 	} else {
 	    nwin = nn;
	    logger.log(1, "Test group is flexible: Number time samples = "+nn+" at "+(resolution/1000)+" S");
	}
	    
	// make up some coeffs.
	double ar = 0.4;
	double as = 0.5;
	double aa = 3.0;
	double bb = 11.0;
	
	// seeing condition probs.
	double pp = 0.6;
	double pa = 0.3;
	double px = 0.1;
	
	double pall = 0.0;
	double pmax = -999.99;
	
	// we want to record new executions so clear out the old ones.	
	loadHistory();
	
	site = new Site("test", Position.phi, Position.ll);

	// TODO calculate next sunrise - this will not work for multiday stuff
	Position sun = Astrometry.getSolarPosition(time);

	// reset the logger timestamp - if we are using sim logging...
	timing.setTime(time);
	logger.setTimeProvider(timing);

	long exec = getExecTime(testGroup);
	
	// records how likely we will execute in each window.
	double diff[] = new double[nwin];
	double cum [] = new double[nwin];
	double max[] = new double[nwin]; // only used by Mons

	for (int i = 0; i < nwin; i++) {
	    diff[i] = Double.NaN;
	    cum[i]  = 0.0;
	    max[i] = 0.0;
	}

	// Is there a group already running and when is it likely to finish
	long cend = 0L; // mark end of period.
	Group cgroup = Scheduling.getCurrentGroup();
	if (cgroup != null) {
	    long cstart = Scheduling.getCurrentGroupStartTime();
	    long cexec  = getExecTime(cgroup);
	    
	    cend = cstart + cexec;
	}

	// store list of reasons why cannot be done
	Vector noCanDo = new Vector();

	long t3 = System.currentTimeMillis();
	for (int i = 0; i < nn; i++) {
	    
	    long t = time + i*(long)resolution;
	    timing.setTime(t);	    

	    // which window is this.

	    int iwin = 0;
	    if (testGroup instanceof MonitorGroup) {
		//int fwin = (int)((time - ((MonitorGroup)testGroup).getStartDate())/
		//((MonitorGroup)testGroup).getPeriod());
		iwin = (int)((t - time)/
			     ((MonitorGroup)testGroup).getPeriod());
		logger.log(1, "Slot number: "+i+" in monitor exec window: "+iwin); 
	    } else {
		iwin = i;
		logger.log(1, "Slot number: "+i+" in single flexible window");
	    }
	    
	    double ps1 = 0.0;
	    double ps2 = 0.0;
	    double ps3 = 0.0;
	    
	    int rank1 = 0;
	    int rank2 = 0;
	    int rank3 = 0;
	    
	    double s1 = 0.0;
	    double s2 = 0.0;
	    double s3 = 0.0;
	    
	    Score met1 = null;
	    Score met2 = null;
	    Score met3 = null;

	    double contention1 = 0.0;
	    double contention2 = 0.0;
	    double contention3 = 0.0;

	    // check if current group will be done yet
	    if (t + exec < cend) {
		// infeasible under any conditions
		logger.log(2,"Test Group not feasible due to current group est completion in: "+((cend - t)/1000)+"S");
		ps1 = 0.0;
		ps2 = 0.0;
		ps3 = 0.0;

	    } else {

		// pr
		logger.log(3,"Seeing = POOR");
	
		if (canDo(testGroup, t, exec, Group.POOR, true).canDo) {	
		    contention1 = getContention(t, Group.POOR);
		    List cand = getCandidates();
		    cp[i] = cand.size();
		    if (contention1 >= 0.0) {
			met1 = getScore(testGroup, t, Group.POOR);
			double score1 = met1.score;
			//long ttx = getTtx(testGroup, t, exec);
			//met1.ttx = ttx;
			cand.add(met1);
			logger.log(3,"Test group scored: "+met1);
			double best1 = displayCandidates(cand);
			s1 = best1;
			rank1 = rank(cand, score1);
			ps1 = sigmoid(rank1, aa, bb);
			// + as*(score1/best1);	
			logger.log(3,"Test group ranked "+rank1+" Pselect = "+ps1);
		    }
		} else {
		    logger.log(3,"Test Group not feasible");
		    ps1 = 0.0;
		}
		
		// av
		logger.log(3,"Seeing = AVER");
	
		if (canDo(testGroup, t, exec, Group.AVERAGE, true).canDo) {	
		    contention2 = getContention(t, Group.AVERAGE);	
		    List cand = getCandidates();
		    ca[i] = cand.size();
		    if (contention2 >= 0.0 && canDo(testGroup, t, exec, Group.AVERAGE, true).canDo) {
			met2 = getScore(testGroup, t, Group.AVERAGE);	
			double score2 = met2.score;
			//long ttx = getTtx(testGroup, t, exec);
			//met2.ttx = ttx;
			cand.add(met2);
			logger.log(3,"Test group scored: "+met2);
			double best2 = displayCandidates(cand);
			s2 = best2;
			rank2 = rank(cand, score2);
			ps2 = sigmoid(rank2, aa, bb);
			// + as*(score2/best2);
			logger.log(3,"Test group ranked "+rank2+" Pselect = "+ps2);
		    }
		} else {
		    logger.log(3,"Test Group not feasible");
		    ps2 = 0.0;
		}
		
		// x
		logger.log(3,"Seeing = EXCELLENT");
	
		CanDo canDoEx = canDo(testGroup, t, exec, Group.EXCELLENT, true);
		if (canDoEx.canDo) {	
		    contention3 = getContention(t, Group.EXCELLENT);	
		    List cand = getCandidates(); 
		    cx[i] = cand.size();
		    if (contention3 >= 0.0 && canDoEx.canDo) {
			met3 = getScore(testGroup, t, Group.EXCELLENT);
			double score3 = met3.score;
			//long ttx = getTtx(testGroup, t, exec);
			//met3.ttx = ttx;
			cand.add(met3);
			logger.log(3,"Test group scored: "+met3);
			double best3 = displayCandidates(cand);
			s3 = best3;
			rank3 = rank(cand, score3);
			ps3 = sigmoid(rank3,aa,bb);
			// + as*(score3/best3);
			logger.log(3,"Test group ranked "+rank3+" Pselect = "+ps3);
		    }
		} else {
		    logger.log(3,"Test Group not feasible");
		    ps3 = 0.0;
		    noCanDo.add(canDoEx.whyNoCanDo);
		}
	    }
	    
	    logger.log(1, "Cand Rank  (PAX): "+rank1+" "+rank2+" "+rank3+" Best scores(PAX): "+s1+" "+s2+" "+s3);
	    logger.log(1, "Cand scores(PAX): "+met1+", "+met2+", "+met3);

	    rp[i] = rank1;
	    ra[i] = rank2;
	    rx[i] = rank3;
	    
	    
	    sp[i] = ps1;
	    sa[i] = ps2;
	    sx[i] = ps3;
	    
	    logger.log(3,sdf.format(new Date(t))+" Contention: "+contention1+" "+contention2+" "+contention3);	    
	    logger.log(3,sdf.format(new Date(t))+ " P Selection(overall) = "+(ps1*pp+ps2*pa+ps3*px));
	    pall = (ps1*pp+ps2*pa+ps3*px);

	    // Factor in the weather at time
	    double pwgood = 0.0;
	    double pwbad  = 0.0;
	  
	    if (weatherState == WeatherMonitoring.WEATHER_GOOD) {
		pwgood = 1.0/(1.0 + Math.exp((t - now - avGoodPeriod + weatherStable)/weatherStable));
		pwbad = 1.0 - pwgood;
	    } else if
		(weatherState == WeatherMonitoring.WEATHER_BAD) {	
		pwbad = 1.0/(1.0 + Math.exp((t - now - avBadPeriod + weatherStable)/weatherStable));
		pwgood = 1.0 - pwbad;
	    } 
	    // weather correction logging.
	    logger.log(1, sdf.format(new Date(t))+ "DT = "+((t - now)/60000)+
		       "M: P_w_good(t) = "+pwgood+" P_w_bad(t) = "+pwbad+" P_corr(t) = "+pall);
	    
	    
	    sall[i] = pall;

	   
	    if (testGroup instanceof MonitorGroup) { 
		// record best positive score this [window] in diff
		if (pall >= 0.0) {
		    if (pall >= max[iwin]) {
			diff[iwin] = pall;
			max[iwin] = pall;
		    }
		}
	    } else {
		// record the differential probability this [slot] in diff
		if (pall >= 0.0)
		    diff[i] = pall;
		
	    }

	} // next time step
	
	logger.setTimeProvider(new SystemTimeProvider());
	
	long t4 = System.currentTimeMillis();	 
	logger.log(1,"Extracted ["+nn+"] measures in ("+(t4-t3)+"ms) = "+ ((t4-t3)/nn)+"ms/test");

	// bin count.
	for (int i=0; i < nn; i++) {
	    long t = time+i*(long)resolution;
	    System.err.println(sdf.format(new Date(t))+" "+rp[i]+" "+ra[i]+" "+rx[i]+
			       " "+sp[i]+" "+sa[i]+" "+sx[i]+
			       " "+cp[i]+" "+ca[i]+" "+cx[i]+
			       " "+sall[i]);	    
	} // next time step/bin
	

	if (testGroup instanceof MonitorGroup) {
	    logger.log(1,"Extracted window exec scores using Monitor mode");
	    double total = 1.0;	
	    double score = 0.0;
	    for (int j=0; j < nwin; j++) {   
		// some of these may be NaN - dont add to total
		if (! Double.isNaN(diff[j])) {
		    score += diff[j];	  		  
		    cum[j] = total*diff[j];
		    total = cum[j];
		}
		System.err.println("Window ["+j+"] Cumulative = "+cum[j]);
	    }	
	    info.score = score/nwin;
	} else { 
	    logger.log(1,"Extracted window exec scores using Flex mode");
	    double total = 0.0;
	    double bestscore  = 0.0;
	    for (int j=0; j < nwin; j++) {
		bestscore = Math.max(bestscore, diff[j]);
		cum[j]    = Math.max(total, diff[j]);
		total     = cum[j];	
		System.err.println("Window ["+j+"] Cumulative = "+cum[j]);
	    }	
	    info.score = bestscore;
	}

	info.differential = diff;
	info.cumulative   = cum; 

	// info.noCanDo = noCanDo .... need to extract counts of same entry?
	StringBuffer noCanDoBuff = new StringBuffer();

	if (noCanDo.size() != 0) {

	    //Create a mapping from reason to count...
	    Map reasons = new HashMap();
	    
	    Iterator ir = noCanDo.iterator();
	    while (ir.hasNext()) {
		//noCanDoBuff.append((String)ir.next());
		String areason = (String)ir.next();
		if (reasons.containsKey(areason)) {
		    int ic = ((Integer)reasons.get(areason)).intValue();
		    ic++;
		    reasons.put(areason, new Integer(ic));
		} else {
		    reasons.put(areason, new Integer(1));
		}
		
	    }
	    
	    Iterator is = reasons.keySet().iterator();
	    while (is.hasNext()) {
		String areason = (String)is.next();
		int    irc     = ((Integer)reasons.get(areason)).intValue();
		double rc = (double)irc/(double)noCanDo.size();
		
		if (is.hasNext())
		    noCanDoBuff.append(areason+":"+rc+", "); 
		else
		    noCanDoBuff.append(areason+":"+rc);

	    }
	    
	}

	info.failureReasons = noCanDoBuff.toString();

	return info;

	//	return pmax;

    }

    /** Run forward simulation with test group.*/
    public void runSim(ConfigurationProperties config, Group testGroup) throws Exception {

	logger.log(1, "Start simulation [SIMRUN]");

	int number = config.getIntValue("number", 1);

	long start = (sdf.parse(config.getProperty("start"))).getTime();
	long end   = (sdf.parse(config.getProperty("end"))).getTime();

	// bin size
        long step = config.getLongValue("step", 60000L);
        int nbin = (int)((end - start)/step) + 1;

        double[] sp = new double[nbin];
	double[] mx = new double[nbin];
	double[] mn = new double[nbin];
	int[] np = new int[nbin];
        for (int i = 0; i < nbin; i++) {
            sp[i] = 0.0;
	    np[i] = 0;
	    mx[i] = -999.0;
	    mn[i] = 999.0;
        }

	// Create a proposal to hold our group.
        Proposal prop = new Proposal("sim");
        prop.setActivationDate(start-30*86400*1000L);
        prop.setExpiryDate(end+30*86400*1000L);
	prop.setUsedTime(0);
	prop.setAllocatedTime(1);

	addProposalToCache(prop);     
        addTestGroup(testGroup);

	int ntest = 0; // count test scheduled

	for (int irun = 0; irun < number; irun++) {

	    // reset the logger timestamp..
	    timing.setTime(start);

	    logger.log(2, "Starting sim run ["+irun+"]");

	    int ng = 0; // count groups scheduled.
	    double stot = 0.0; // total score
	    double vtot = 0.0; // total utility value..

	    loadHistory();

	    long time = start;
	    while (time < end) {

		int isee = Group.POOR;
		double rnd = Math.random();
		if (rnd < 0.15)
		    isee = Group.EXCELLENT;
		else if
		    (rnd < 0.25)
		    isee = Group.AVERAGE;
		else if
		    (rnd < 0.9)
		    isee = Group.POOR;
		else
		    isee = Group.CRAP;

		//		double gc = getContention(time, Group.EXCELLENT);
		double gc = getContention(time, isee);
		List available = getCandidates();
		
		Score top = getBestGroup(available);
		Group best = top.group;

		if (best == null) {
		    logger.log(2, "["+irun+"] ["+Group.toSeeingString(isee)+"]  Score [0.0] No available groups");
		    
		    time += 10*60*1000L; // 10 minute sleep..
		    timing.setTime(time);

		} else {
		    
		    logger.log(2, 
			       "["+irun+"] ["+Group.toSeeingString(isee)+
			       "] Score ["+top.score+"] Best of "+
			       available.size()+" "+best.getFullPath());
		    
		    if (best == testGroup) {
			logger.log(1, "***** Test group was selected *****");
			displayCandidates(available);
			ntest++;
		    }
		    
		    int ibin = (int)((time - start)/step)+1;
                    sp[ibin] += top.score;
                    np[ibin]++;
		    if (top.score > mx[ibin])
			mx[ibin] = top.score;

		    if (top.score < mn[ibin])
                        mn[ibin] = top.score;

		    long exec = getExecTime(best);

		    time += exec;
		    timing.setTime(time);

		    // we are updating the stats for this group so it dont get done again
		    // at least in the current window...
		    updateHistory(best, time);
		    ng++;
		    stot += Math.max(top.score, 0.0);
		    vtot += Math.max(top.score, 0.0)*(double)exec;
		} 

	    } //next time

	    logger.log(1, "Run ["+irun+"] Summary: Num.Groups = "+ng+", Tot.score = "+stot+", Tot.value = "+vtot);
	    
	} // next run

	for (int ib = 0; ib < nbin; ib++) {
	    long t = start+ib*step;
            System.err.println(sdf.format(new Date(t))+" "+((double)sp[ib]/(double)np[ib])+" "+mn[ib]+" "+mx[ib]);
	}
	
	logger.log(1, "Sim summary: Test group was scheduled "+ntest+"/"+number+" runs");

    }
    
    public void runScore(ConfigurationProperties config, Group testGroup) throws Exception {

	logger.log(1, "Start simulation [GRPSCORE]");

	long start = (sdf.parse(config.getProperty("start"))).getTime();
        long end   = (sdf.parse(config.getProperty("end"))).getTime();
	
	long step = config.getLongValue("step", 60000L);

	// reset the logger timestamp..
	timing.setTime(start);

	logger.log(2, "Starting scoring run...");

	loadHistory();
	
	long exec = getExecTime(testGroup);

	long time = start;
	while (time < end) {

	    if (canDo(testGroup, time, exec, Group.EXCELLENT, false).canDo) {
		double score1 = getScore(testGroup, time, Group.POOR).score;
		double score2 = getScore(testGroup, time, Group.AVERAGE).score;
		double score3 = getScore(testGroup, time, Group.EXCELLENT).score;
		
		logger.log(1, "P= "+score1+" A= "+score2+" X= "+score3);
	    } else {
		logger.log(1, "P= 0.0 A= 0.0 X= 0.0");
	    }

	    time += step;
	    timing.setTime(time);
	}
       
    }

    /** Run a forward simulation count in bins what time the test group gets scheduled, we can
     * then compare the profile with the contention plot from runTest(). This should be run over
     * a single night in which the test group is either enabled for the whole night or on
     * several windows which should match bin boundaries.
     */
    public void runSimBin(ConfigurationProperties config, Group testGroup) throws Exception {

	logger.log(1, "Start simulation [SIMBIN]");

        long start = (sdf.parse(config.getProperty("start"))).getTime();
        long end   = (sdf.parse(config.getProperty("end"))).getTime();

	int number = config.getIntValue("number", 1);

	// bin size
        long step = config.getLongValue("step", 60000L);

	int nbin = (int)((end - start)/step) + 1;

	int[] count = new int[nbin];
	for (int i = 0; i < nbin; i++) {
	    count[i] = 0;
	}

        // reset the logger timestamp..
        timing.setTime(start);

	// Create a proposal to hold our group.
        Proposal prop = new Proposal("sim");
        prop.setActivationDate(start-30*86400*1000L);
        prop.setExpiryDate(end+30*86400*1000L);
        prop.setUsedTime(0);
        prop.setAllocatedTime(1);

        addProposalToCache(prop);
        addTestGroup(testGroup);

	int nsel = 0;
	
        for (int irun = 0; irun < number; irun++) {

	    boolean tsel = false; // see if we get selected.

            // reset the logger timestamp..
            timing.setTime(start);

            logger.log(2, "Starting sim run ["+irun+"]");

            int ng = 0; // count groups scheduled.
            double stot = 0.0; // total score

            loadHistory();

            long time = start;
            while (time < end) {

                int isee = Group.POOR;

		double rnd = Math.random();
                if (rnd < 0.15)
                    isee = Group.EXCELLENT;
                else if
		    (rnd < 0.25)
                    isee = Group.AVERAGE;
                else if
		    (rnd < 0.9)
                    isee = Group.POOR;

		else
                    isee = Group.CRAP;

                //              double gc = getContention(time, Group.EXCELLENT);
                double gc = getContention(time, isee);
                List available = getCandidates();

                Score top = getBestGroup(available);
                Group best = top.group;

                if (best == null) {
                    logger.log(3, "["+irun+"] ["+Group.toSeeingString(isee)+"]  Score [0.0] No available groups");

                    time += 10*60*1000L; // 10 minute sleep..
                    timing.setTime(time);

                } else {

                    logger.log(3,
                               "["+irun+"] ["+Group.toSeeingString(isee)+
                               "] Score ["+top.score+"] Best of "+
                               available.size()+" "+best.getFullPath());

                    if (best == testGroup) {

			int bin = (int)(((time - start))/step);
			count[bin]++;			
			logger.log(1, "Test group was selected at "+sdf.format(new Date(time))+
				   " DT= "+((time - start)/1000)+" Bin= "+bin);
			tsel = true;
			nsel++;

		    }
		    
                    long exec = getExecTime(best);
		    
		    time += exec;
		    timing.setTime(time);
		    
                    // we are updating the stats for this group so it dont get done again
                    // at least in the current window...
                    updateHistory(best, time);
                    
                }

            } //next time

	    logger.log(1, "Run ["+irun+"] Group "+(tsel ? " was " : " was not ")+" selected");

        } // next run

	// bin count.
	for (int i=0; i < nbin; i++) {
	    long t = start+i*step;
	    System.err.println(sdf.format(new Date(t))+" "+count[i]);
	}

    }

    /** RUns a number of simulaitons over a night, counting how often the test group is selected,
     * this should be a monitor group with multiple executions.
     */
    public void runExpectBin(ConfigurationProperties config, Group testGroup) throws Exception {

	logger.log(1, "Start simulation [EXPECTATION]");

        long start = (sdf.parse(config.getProperty("start"))).getTime();
        long end   = (sdf.parse(config.getProperty("end"))).getTime();

	int number = config.getIntValue("number", 1);
	int nx = config.getIntValue("nx");

	logger.log(1, "Running from:"+sdf.format(new Date(start))+" until "+sdf.format(new Date(end))+
		   " Sim runs = "+number+" expecting "+nx+" exec windows");

	int[] count = new int[nx];
	for (int i = 0; i < nx; i++) {
	    count[i] = 0;
	}

        // reset the logger timestamp..
        timing.setTime(start);

	// Create a proposal to hold our group.
        Proposal prop = new Proposal("sim");
        prop.setActivationDate(start-30*86400*1000L);
        prop.setExpiryDate(end+30*86400*1000L);
        prop.setUsedTime(0);
        prop.setAllocatedTime(1);

        addProposalToCache(prop);
        addTestGroup(testGroup);

	long t1 = System.currentTimeMillis();
	for (int irun = 0; irun < number; irun++) {
	 
            // reset the logger timestamp..
            timing.setTime(start);

            logger.log(2, "Starting sim run ["+irun+"]");
	             
            loadHistory();

	    int nsel = 0; // count no of time test is selected

            long time = start;
            while (time < end) {

                int isee = Group.POOR;

		double rnd = Math.random();
                if (rnd < 0.15)
                    isee = Group.EXCELLENT;
                else if
		    (rnd < 0.25)
                    isee = Group.AVERAGE;
                else if
		    (rnd < 0.9)
                    isee = Group.POOR;

		else
                    isee = Group.CRAP;
          
                double gc = getContention(time, isee);
                List available = getCandidates();

                Score top = getBestGroup(available);
                Group best = top.group;

                if (best == null) {
                    logger.log(2, "["+irun+"] ["+Group.toSeeingString(isee)+"]  Score [0.0] No available groups");
		    
                    time += 10*60*1000L; // 10 minute sleep..
                    timing.setTime(time);
		    
                } else {
		    
                    logger.log(2,
                               "["+irun+"] ["+Group.toSeeingString(isee)+
                               "] Score ["+top.score+"] Best of "+
                               available.size()+" "+best.getFullPath());
		    
                    if (best == testGroup) {
			
			logger.log(1, "Test group was selected on run ["+irun+"] at "+sdf.format(new Date(time))+
				   " DT= "+((time - start)/1000));			
			nsel++;
			
		    }
		    
                    long exec = getExecTime(best);
		    
		    time += exec;
		    timing.setTime(time);
		    
                    // we are updating the stats for this group so it dont get done again
                    // at least in the current window...
                    updateHistory(best, time);
                    
                }

            } //next time

	    count[nsel]++; 

	    logger.log(1, "Run ["+irun+"] Test Group was selected "+nsel+" times");
	    
        } // next run
	long t2 = System.currentTimeMillis();

	// bin count.
	double ex = 0.0;
	double sx = 0.0;
	for (int i=0; i < nx; i++) {
	    ex += i*count[i];
	    sx += count[i];
	    System.err.println(""+i+" "+count[i]);
	}
	ex = ex/sx;
	System.err.println("Expectation value: "+ex);

	System.err.println("Completed "+number+" runs in "+((t2-t1)/1000)+"S, "+((t2-t1)/number)+"MS per run");
	
    }


    public double displayCandidates(List candidates) {

	Score best = new Score(null, -1000.0, 0L);
    
	Iterator ic = candidates.iterator();
	while (ic.hasNext()) {
	    Score score = (Score)ic.next();
	    if (score.score > best.score) 
		best = score;
	    logger.log(3, "Group "+score.group.getFullPath()+" : "+score.score);
	}

	logger.log(2, "Best group of "+candidates.size()+" was: "+best);	
	
	return best.score;

    }

    public int rank(List candidates, double test) {

	int count = 0;
	Iterator ic = candidates.iterator();
	while (ic.hasNext()) {
	    Score score = (Score)ic.next();
	    if (score.score > test) 
		count++;	  
	}
	
	return count+1;

    }

    public Score getBestGroup(List candidates) {
 
	Score best = new Score(null, -9999.9, 0L);

	Iterator ic = candidates.iterator();
	while (ic.hasNext()) {
	    Score score = (Score)ic.next();
	    if (score.score > best.score) { 
		best = score;
	    }
	}

	return best;

    }

    /** Sigmoid like function to smooth out scoring probability.*/
    public double sigmoid(double arg, double aa, double bb) {
	return 1.0/(1.0+Math.exp(arg - aa)/bb);
    }
    
  //   /** Start the cache loader thread.*/
//     public void startLoader() {
// 	Loader loader = new Loader();
// 	loader.start();
//     }


    public class Score {

	public Group group;

	public double score;

	public double ttx;

	Score(Group group, double score, long ttx) {
	    this.group = group;
	    this.score = score;
	    this.ttx   = ttx;
	}

	public String toString() { return group.getFullPath()+" : "+score+", ttx="+ttx;}

    }

    /** Class to hold scoring return info.
     * all content is set publicly.
     */
    public static class ScoreInfo {

	public double score;

	public double[] differential;

	public double[] cumulative;

	public String failureReasons;

	public ScoreInfo() {}

	public String toString() {
	    return "Score="+score+
		", Differential="+differential+
		", Cumulative="+cumulative+
		" NoCanDo? : "+failureReasons;
	}
 
    }

    public static class CanDo {

	public boolean canDo;

	public String whyNoCanDo;

	public CanDo(boolean canDo, String whyNoCanDo) {
	    this.canDo = canDo;
	    this.whyNoCanDo = whyNoCanDo;
	}

	public CanDo(boolean canDo) {
	    this.canDo = canDo;
	}

	public String toString() {
	    return (canDo ? "CanDo" : "NoCanDo because: "+whyNoCanDo);
	}

    }

  //   class Loader extends Thread {

// 	public Loader() {
// 	    super();
// 	}

// 	public void run() {
// 	    while (true) {

// 		// reload cache		
// 		try {
// 		    loadCache();
// 		} catch (Exception e1) {
// 		    e1.printStackTrace();
// 		}
		
// 		// reload uA windows
// 		try {
// 		    AvailabilityModel am = (AvailabilityModel)Naming.lookup("rmi://localhost/AvailabilityModel");
		   
// 		    List windows = am.getUnavailableTimes(0L,0L);		  
// 		    setUnavailableWindows(windows);
// 		} catch (Exception e2) {
// 		    e2.printStackTrace();
// 		}
		    

// 		try {Thread.sleep(cacheReloadInterval);} catch (InterruptedException ix) {}

// 	    }

// 	}

//    }


}
